多租户多数据库演示
通过在请求 Header 中传递 x-tenant-id,同一个 API 接口可以返回来自不同数据库的数据:
GET /api/v1
Header: x-tenant-id: default → MySQL 数据库(Prisma)
Header: x-tenant-id: default1 → MySQL 数据库(Prisma,不同库)
Header: x-tenant-id: default2 → PostgreSQL 数据库(Prisma)
Header: x-tenant-id: default3 → PostgreSQL 数据库(TypeORM)
text
不同租户 → 不同数据库 → 不同 ORM 方案 → 返回不同数据。这就是多租户动态数据库的核心效果。
架构演进路线
第一阶段:单库架构
前端请求 → NestJS → ORM → 单一数据库
text
数据集中存储,适合小型项目。但存在以下问题:
| 问题 | 说明 |
|---|---|
| 数据集中 | 所有数据在一个库中,查询压力大 |
| 安全性低 | 一个库被攻破,所有数据泄露 |
| 扩展性差 | 单库有物理上限(存储、连接数、QPS) |
第二阶段:主从架构
前端请求 → NestJS → ORM → 主库(写)
→ 从库(读)
→ Redis(缓存/队列)
text
主库负责写操作,从库负责读操作。Redis 作为缓存层加速热点数据查询,或作为消息队列缓冲写请求。
这是绝大多数生产环境采用的架构,解决了单库的安全性和可扩展性问题。
第三阶段:多数据库架构
前端请求 → 网关层 → 微服务A(Prisma)→ MySQL
→ 微服务B(TypeORM)→ PostgreSQL
→ 微服务C(Mongoose)→ MongoDB
text
不同业务模块使用不同类型的数据库:
| 数据类型 | 推荐数据库 | 理由 |
|---|---|---|
| 业务数据(订单、用户) | MySQL / PostgreSQL | 事务支持、关联查询 |
| 文档/二进制数据 | MongoDB | 文档存储,非结构化数据友好 |
| 消息/日志 | MongoDB / Redis | 写入频繁,Schema 灵活 |
| 缓存/会话 | Redis | 内存级速度 |
第四阶段:多租户动态架构
前端请求(Header: x-tenant-id)
→ 网关层(解析租户信息)
→ 配置中心(读取租户数据库配置)
→ 动态创建 ORM 连接
→ 操作对应数据库
text
逻辑隔离 vs 物理隔离:
| 隔离方式 | 说明 | 优势 | 劣势 |
|---|---|---|---|
| 逻辑隔离 | 多租户共享一个数据库,通过字段区分 | 资源利用率高,运维成本低 | 安全性相对较低 |
| 物理隔离 | 每个租户独立数据库 | 数据安全性和完整性最高 | 资源消耗大,运维成本高 |
物理隔离后还可以继续"套娃"——为高流量租户做读写分离、分库分表等扩展。
多 ORM 架构的应用场景
何时需要多 ORM
绝大多数业务场景不需要多 ORM。一个 ORM 对接一种类型的数据库就够了。只有在以下场景才需要:
| 场景 | 说明 |
|---|---|
| 低代码平台 | 用户自带数据库,类型和版本不确定 |
| 跨系统数据集成 | 需要对接不同类型的遗留系统 |
| 数据库动态配置 | 用户可以指定使用哪种数据库 |
单 ORM vs 多 ORM
单 ORM + 多数据库(常见)
→ 同一套代码操作 MySQL/PostgreSQL/SQLite
→ 成本低,维护简单
→ 适合绝大多数业务
多 ORM + 多数据库(特殊场景)
→ Prisma 对接 MySQL,TypeORM 对接 PostgreSQL,Mongoose 对接 MongoDB
→ 代码量翻倍,维护复杂
→ 仅在数据库类型/版本不确定时使用
text
多 ORM 架构的问题
| 问题 | 说明 | 缓解措施 |
|---|---|---|
| 多样性管理 | 不同 ORM 的 API 风格不同 | 中间封装一层统一接口 |
| 迁移复杂性 | 不同数据库的 SQL 语法不同 | 使用 ORM 抽象层,避免原生 SQL |
| 配置维护 | 不同数据库配置格式不同 | 配置中心统一管理 |
架构设计核心思想
数据库架构设计并不复杂,核心是找出薄弱环节并针对性扩展:
性能瓶颈在哪?
├── 网关层 → 负载均衡
├── ORM 层 → 更换/增加 ORM 方案
├── 数据库写性能 → 主从复制、分库分表
├── 数据库读性能 → 从库扩展、缓存层
└── 数据安全性 → 物理隔离、加密存储
text
扩展原则:哪个环节出现瓶颈,就扩展哪个环节。架构是演进而来的,不是一开始就设计出来的。
动态连接实现要点
演示中的多租户数据库连接并非使用 .env 静态配置,而是动态读取连接信息:
// 核心思路(简化)
const tenantConnections = new Map<string, any>();
function getTenantConnection(tenantId: string) {
if (tenantConnections.has(tenantId)) {
return tenantConnections.get(tenantId); // 复用已有连接
}
// 从配置中心读取该租户的数据库配置
const config = getConfigByTenant(tenantId);
// 根据配置选择 ORM 方案并创建连接
const connection = createORMConnection(config);
tenantConnections.set(tenantId, connection);
return connection;
}
typescript
关键点:
- 连接复用:同一租户的请求复用已有连接(连接池),不每次创建新连接
- 配置动态化:数据库连接信息从配置中心读取,而非硬编码在
.env中 - ORM 选择:根据数据库类型自动选择对应的 ORM 方案
架构师的思维方式
架构设计不是堆技术,而是根据业务场景做合理取舍:
- 简单商城 → 单库架构 + 主从复制足够
- 多租户 SaaS → 物理隔离,每个租户独立数据库
- 低代码平台 → 多 ORM 动态连接,兼容用户侧各种数据库
- 大型系统 → 微服务拆分,每个服务独立选择数据库和 ORM
经验不足不是问题——借助 AI 工具和系统化方法论,可以快速了解不熟悉的领域。关键是倒逼学习:遇到具体业务需求时,针对性地学习对应的技术方案。
↑